// Uncomment the following #define if you get XML serialization exceptions while deserializing objects.
// Doing so will make the library to first read the complete XML into the string so you can quick watch and/or copy, then deserialize the object from that string.
// #define DEBUG_XML_SERIALIZATION
using System;
using System.IO;
using System.Text;
using System.Xml.Serialization;
using Microsoft.Isam.Esent.Interop;

namespace Microsoft.Isam.Esent.Serialization.Attributes
{
	/// <summary>Column containing the object serialized into XML encoded in UTF8.</summary>
	/// <remarks>
	/// <para>This column type is here mainly for backward compatibility.
	/// For the new projects, consider using <see cref="EseDCSAttribute">[EseDCS]</see> instead.</para>
	/// <para>May be applied to a field/property of any type suitable for serialization with XmlSerializer class.</para>
	/// <para>The underlying ESENT column type is JET_coltypLongText, ASCII-encoded, holding a UTF-8 XML stream.</para>
	/// <para>The attribute instance holds the System.Xml.Serialization.XmlSerializer object that performs the [de]serialization.<br/>
	/// Please, don't apply this attribute to the fields/properties of type "object", provide the correct type.
	/// the correct type that will be serialized must be passed to the verifyTypeSupport method, to construct the right XmlSerializer instance.<br/>
	/// <br/>
	/// The <see cref="System.Xml.Serialization.XmlSerializer">documentation on XmlSerializer</see> says:<br/>
	/// <i>"This type is safe for multithreaded operations".</i><br/>
	/// That's why no locks is ever needed, even when different threads are [de]serializing the same column of different records,
	/// which is perfectly legal way of using the library.</para>
	/// </remarks>
	public sealed class EseXmlSerializedAttribute : EseColumnAttrubuteBase
	{
		/// <summary>Initialize with the default column name, which is the name of the field/property.</summary>
		public EseXmlSerializedAttribute() { }
		/// <summary>Initialize with non-default column name.</summary>
		public EseXmlSerializedAttribute( string _columnName ) : base( _columnName ) { }

		/// <summary>Is always true.</summary>
		public override bool bFieldNullable { get { return true; } }

		/// <summary>Get column definition.</summary>
		public override JET_COLUMNDEF getColumnDef()
		{
			// http://stackoverflow.com/questions/2391112
			JET_COLUMNDEF res = new JET_COLUMNDEF();
			res.coltyp = JET_coltyp.LongText;
			res.grbit = ColumndefGrbit.ColumnTagged;

			if( Params.bUseColumnCompressionIfAvailable )
			{
				var ver = Environment.OSVersion.Version;
				if( ver.Major > 6 || ( ver.Major == 6 && ver.Minor >= 1 ) )
					res.grbit |= Microsoft.Isam.Esent.Interop.Windows7.Windows7Grbits.ColumnCompressed;
			}
			res.cp = JET_CP.ASCII;
			return res;
		}

		XmlSerializer m_serializer;
		/// <summary>Construct the XML serializer for the specified type.</summary>
		/// <param name="t">The type of the field/property this attribute is applied.</param>
		public override void verifyTypeSupport( Type t )
		{
			m_serializer = new XmlSerializer( t );
		}

		/// <summary>Store the column value in the database.</summary>
		public override void Serialize( EseCursorBase cur, JET_COLUMNID idColumn, object value, bool bNewRecord )
		{
			using( var stm = new ColumnStream( cur.idSession, cur.idTable, idColumn ) )
			{
				using( var sw = new StreamWriter( stm, Encoding.UTF8 ) )
				{
					m_serializer.Serialize( sw, value );
					sw.Flush();
				}

				// TODO [low]: if the ( current size - new size < 4kb ), then append spaces/zeros instead of resizing the column. The comments inside the SetLength method suggest that shrinking the column is very ineffective for large values.
				if( stm.Position < stm.Length )
					stm.SetLength( stm.Position );
			}
		}

		/// <summary>Retrieve the column value from the DB.</summary>
		public override object Deserialize( EseCursorBase cur, JET_COLUMNID idColumn )
		{
#if DEBUG_XML_SERIALIZATION
			string strXml;
			using( var stm = new ColumnStream( cur.idSession, cur.idTable, idColumn ) )
			using( var sr = new StreamReader( stm, Encoding.UTF8 ) )
				strXml = sr.ReadToEnd();

			using( var sr = new StringReader( strXml ) )
				return m_serializer.Deserialize( sr );
#else
			using( var stm = new ColumnStream( cur.idSession, cur.idTable, idColumn ) )
			{
				if( stm.Length < 1 )
					return null;
				using( var sr = new StreamReader( stm, Encoding.UTF8 ) )
					return m_serializer.Deserialize( sr );
			}
#endif
		}
	}
}